package service

import (
	"context"
	"devops/config"
	"devops/models"
	"devops/pkg/jenkins/api_type"
	"devops/pkg/jenkins/api_type/scm_script"
	"devops/pkg/jenkins/api_type/script"
	"devops/pkg/tool"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/bndr/gojenkins"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	tsf "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tsf/v20180326"
)

var jks *gojenkins.Jenkins

type devOps struct {
	jks       *gojenkins.Jenkins
	tsfClient *tsf.Client
}

type JenkinsOpt func(*JenkinsOpts)

type JenkinsOpts struct {
	Env        string
	Jksfile    string
	Max        int
	Days       int
	CronExpr   string
	SecretUuid string
}

func Jenkinsfile(file string) JenkinsOpt {
	return func(o *JenkinsOpts) {
		o.Jksfile = file
	}
}

func Env(env string) JenkinsOpt {
	return func(o *JenkinsOpts) {
		o.Env = env
	}
}

func Discarder(days, max int) JenkinsOpt {
	return func(o *JenkinsOpts) {
		o.Max = max
		o.Days = days
	}
}

func CronExpr(expr string) JenkinsOpt {
	return func(o *JenkinsOpts) {
		o.CronExpr = expr
	}
}

func SecretUuid(uuid string) JenkinsOpt {
	return func(o *JenkinsOpts) {
		o.SecretUuid = uuid
	}
}

func NewDevOps() *devOps {
	devOps := &devOps{}
	err := devOps.init()
	if err != nil {
		return nil
	}
	return devOps
}

func (d *devOps) init() error {
	var err error
	if jks == nil {
		jenkins := gojenkins.CreateJenkins(nil, config.JksUrl, config.JksUser, config.JksPWD)
		jks, err = jenkins.Init(context.TODO())
	}

	d.jks = jks

	if d.tsfClient == nil {
		credential := common.NewCredential(config.TsfSecretID, config.TsfSecretKey)
		cpf := profile.NewClientProfile()
		cpf.HttpProfile.Scheme = "http"
		cpf.HttpProfile.Endpoint = "tsf.tencentcloudapi.com"
		d.tsfClient, err = tsf.NewClient(credential, "", cpf)
	}
	return err
}

func (d *devOps) JobPipines(ctx context.Context, jobName string) []models.JobInfo {
	endpoint := fmt.Sprintf("/job/%s/wfapi/runs", jobName)
	qr := map[string]string{
		"fullStages": "true",
	}
	info := make([]models.JobInfo, 0)
	d.jks.Requester.GetJSON(ctx, endpoint, &info, qr)
	return info
}

func (d *devOps) ExecutionNodeDescribe(jobName string, folder string, pipelineID int, number int) *api_type.WfapiDescribe {
	endpoint := fmt.Sprintf("/job/%s/job/%s/%d/execution/node/%d/wfapi/describe", folder, jobName, pipelineID, number)
	info := new(api_type.WfapiDescribe)
	d.jks.Requester.GetJSON(context.Background(), endpoint, info, nil)
	return info
}

func (d *devOps) ExecutionNodeLog(jobName string, folder string, pipelineID int, number int) *api_type.WfapiLog {
	endpoint := fmt.Sprintf("/job/%s/job/%s/%d/execution/node/%d/wfapi/log", folder, jobName, pipelineID, number)
	info := new(api_type.WfapiLog)
	d.jks.Requester.GetJSON(context.Background(), endpoint, info, nil)
	return info
}

func (d *devOps) GetPiplineRunStages(jobName string, folder string, piplineID int) ([]api_type.PipelineRunStage, error) {
	ctx := context.Background()
	endpoint := fmt.Sprintf("/blue/rest/organizations/jenkins/pipelines/%s/pipelines/%s/runs/%d/nodes", folder, jobName, piplineID)
	jksResp := make([]api_type.PipelineRunStage, 0)
	_, err := d.jks.Requester.Get(ctx, endpoint, &jksResp, nil)
	if err != nil {
		return nil, err
	}
	return jksResp, nil
}

func (d *devOps) CreateItem(jobName string, folder string) error {
	ctx := context.Background()
	endpoint := fmt.Sprintf("/job/%s/createItem", folder)
	qr := map[string]string{
		"name": jobName,
		"mode": "org.jenkinsci.plugins.workflow.job.WorkflowJob",
	}
	resp, err := d.jks.Requester.Post(ctx, endpoint, nil, nil, qr)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("Jenkins创建失败 StatusCode:" + resp.Status)
	}
	return nil
}

func (d *devOps) ConfigSubmit(ci models.CI, folder string, opts ...JenkinsOpt) error {
	ctx := context.Background()
	endpoint := fmt.Sprintf("/job/%s/job/%s/configSubmit", folder, ci.Name)
	qr := map[string]string{"json": d.updateCfg(ci, opts...)}
	resp, err := d.jks.Requester.Post(ctx, endpoint, nil, nil, qr)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("更新配置失败 code:" + resp.Status)
	}
	return nil
}

func (d *devOps) updateCfg(ci models.CI, opts ...JenkinsOpt) string {
	// 初始值
	discarder := models.GetCiDiscarder(ci.ID)
	jksOpt := JenkinsOpts{
		Jksfile:    ci.Jenkinsfile,
		Max:        discarder.Max,
		Days:       discarder.Day,
		CronExpr:   "",
		SecretUuid: ci.CredentialsId,
	}
	timing := models.GetCiTiming(ci.ID)
	if timing != nil {
		jksOpt.CronExpr = tool.CrontabExpr(tool.StringToIntSlice(timing.Week, ","), timing.Hour, timing.Minute)
	}
	for _, o := range opts {
		o(&jksOpt)
	}

	// 静态 jenkinsfile
	if ci.JenkinsfileType == 0 {
		jksOpt.Env = d.GetEnvStringGit(ci)
		piplineData := script.PiplineScript{}
		json.Unmarshal([]byte(script.PiplineScriptJson), &piplineData)
		// 设置 jenkinsfile
		piplineData.Definition.Script = jksOpt.Jksfile
		// 设置环境变量
		piplineData.Properties.OrgJenkinsciPluginsEnvinjectEnvInjectJobProperty.Info.PropertiesContent = d.GetEnvStringGit(ci)
		// 设置丢弃构建
		piplineData.Properties.JenkinsModelBuildDiscarderProperty.Strategy.DaysToKeepStr = strconv.Itoa(jksOpt.Days)
		piplineData.Properties.JenkinsModelBuildDiscarderProperty.Strategy.NumToKeepStr = strconv.Itoa(jksOpt.Max)
		// 设置定时构建
		piplineData.Properties.OrgJenkinsciPluginsWorkflowJobPropertiesPipelineTriggersJobProperty.Triggers.HudsonTriggersTimerTrigger.Spec = jksOpt.CronExpr
		cfg, _ := json.Marshal(&piplineData)
		return string(cfg)
	}

	// scm jenkinsfile
	piplineData := scm_script.PiplineSCM{}
	json.Unmarshal([]byte(scm_script.PiplineSCMJson), &piplineData)
	// 设置SCM
	piplineData.Definition.Scm.UserRemoteConfigs.URL = ci.RepositoryUrl
	// 设置环境变量
	piplineData.Properties.OrgJenkinsciPluginsEnvinjectEnvInjectJobProperty.Info.PropertiesContent = d.GetEnvString(ci)
	// 设置丢弃构建
	piplineData.Properties.JenkinsModelBuildDiscarderProperty.Strategy.DaysToKeepStr = strconv.Itoa(jksOpt.Days)
	piplineData.Properties.JenkinsModelBuildDiscarderProperty.Strategy.NumToKeepStr = strconv.Itoa(jksOpt.Max)
	// 设置定时构建
	piplineData.Properties.OrgJenkinsciPluginsWorkflowJobPropertiesPipelineTriggersJobProperty.Triggers.HudsonTriggersTimerTrigger.Spec = jksOpt.CronExpr
	cfg, _ := json.Marshal(&piplineData)
	return string(cfg)
}

func (d *devOps) CreateFolder(name string) (*gojenkins.Folder, error) {
	ctx := context.Background()
	return d.jks.CreateFolder(ctx, name)
}

func (d *devOps) DeleteFolder(name string) error {
	ctx := context.Background()
	endpoint := fmt.Sprintf("/job/%s/doDelete", name)
	resp, err := d.jks.Requester.Post(ctx, endpoint, nil, nil, nil)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("Jenkins删除失败 Status:" + resp.Status)
	}
	return nil
}

func (d *devOps) GetLastBuild(jobName string, folder string) (gojenkins.JobBuild, error) {

	// 有bug版本
	// 由于获取 jenkins 最后一条 build id
	// 不能感知被删除的id， 所以放到数据库做
	//ctx := context.Background()
	//builds, err := d.jks.GetAllBuildIds(ctx, folder+"/job/"+jobName)
	//if err != nil {
	//	return gojenkins.JobBuild{}, err
	//}
	//if len(builds) < 1 {
	//	return gojenkins.JobBuild{}, nil
	//}
	//return builds[0], nil
	return gojenkins.JobBuild{}, nil
}

func (d *devOps) RunPipeline(folder string, jobName string) (*api_type.PipelineRun, error) {
	endpoint := fmt.Sprintf("/blue/rest/organizations/jenkins/pipelines/%s/pipelines/%s/runs/", folder, jobName)

	ctx := context.Background()
	ar := gojenkins.NewAPIRequest("POST", endpoint, nil)
	if err := d.jks.Requester.SetCrumb(ctx, ar); err != nil {
		return nil, err
	}
	ar.SetHeader("Content-Type", "application/json")
	runs := api_type.PipelineRun{}
	_, err := d.jks.Requester.Do(ctx, ar, &runs, nil)
	if err != nil {
		return nil, err
	}
	return &runs, nil
}

func (d *devOps) TestReport(jobName string, folder string, buildNumber int) (*gojenkins.TestResult, error) {
	ctx := context.Background()
	build, err := d.jks.GetBuild(ctx, folder+"/job/"+jobName, int64(buildNumber))
	if err != nil {
		return nil, err
	}
	return build.GetResultSet(ctx)
}

func (d *devOps) Artifacts(jobName string, folder string, buildNumber int) ([]api_type.Atifacts, error) {

	ctx := context.Background()
	endpoint := fmt.Sprintf("/blue/rest/organizations/jenkins/pipelines/%s/pipelines/%s/runs/%d/artifacts", folder, jobName, buildNumber)
	jksResp := make([]api_type.Atifacts, 0)
	_, err := d.jks.Requester.Get(ctx, endpoint, &jksResp, nil)
	if err != nil {
		return nil, err
	}
	return jksResp, nil
}

func (d *devOps) CreateSecret(folder string, token string, uuid string) error {
	j := fmt.Sprintf(`{"": "4", "credentials": {"secret": "%s", "$redact": "secret", "id": "%s", "description": "", "stapler-class": "org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl", "$class": "org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl"}, "Jenkins-Crumb": ""}`, token, uuid)
	qr := map[string]string{"json": j}
	endpoint := fmt.Sprintf("/job/%s/credentials/store/folder/domain/_/createCredentials", folder)
	resp, err := d.jks.Requester.Post(context.Background(), endpoint, nil, nil, qr)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("创建凭证失败 Code:" + resp.Status)
	}
	return nil
}

func (d *devOps) CreateUserPwdSecret(folder string, username string, pwd string, uuid string) error {
	j := fmt.Sprintf(`{"": "0", "credentials": {"username": "%s", "password": "%s", "$redact": "password", "id": "%s", "description": "", "stapler-class": "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl", "$class": "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"}, "Jenkins-Crumb": ""}`, username, pwd, uuid)
	qr := map[string]string{"json": j}
	endpoint := fmt.Sprintf("/job/%s/credentials/store/folder/domain/_/createCredentials", folder)
	resp, err := d.jks.Requester.Post(context.Background(), endpoint, nil, nil, qr)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("创建凭证失败 Code:" + resp.Status)
	}
	return nil
}

func (d *devOps) DeleteSecret(folder string, uuid string) error {
	qr := map[string]string{"json": `{"Jenkins-Crumb": ""}`}
	endpoint := fmt.Sprintf("/job/%s/credentials/store/folder/domain/_/credential/%s/doDelete", folder, uuid)
	resp, err := d.jks.Requester.Post(context.Background(), endpoint, nil, nil, qr)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("删除凭证失败 Code:" + resp.Status)
	}
	return nil
}

func (d *devOps) RenameJob(jobName string, folder string, newName string) error {
	ctx := context.Background()
	job, err := d.jks.GetSubJob(ctx, folder, jobName)
	if err != nil {
		return err
	}
	_, err = job.Rename(ctx, newName)
	return err
}

func (d *devOps) DeletePipelineBuild(jobName string, folder string, buildNumber int) error {
	ctx := context.Background()
	endpoint := fmt.Sprintf("/job/%s/job/%s/%d/doDelete", folder, jobName, buildNumber)
	resp, err := d.jks.Requester.Post(ctx, endpoint, nil, nil, nil)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New("删除Build失败 Code:" + resp.Status)
	}
	return nil
}

func (d *devOps) PiplineBuildConsole(jobName string, folder string, buildNumber int) (string, error) {
	ctx := context.Background()
	endpoint := fmt.Sprintf("/job/%s/job/%s/%d/consoleText", folder, jobName, buildNumber)
	var output string
	resp, err := d.jks.Requester.Get(ctx, endpoint, &output, nil)
	if err != nil || resp.StatusCode != 200 {
		return "", err
	}
	return output, nil
}

func (d *devOps) GetBuildIDs(jobName string, folder string) ([]int, error) {
	buildIDs, err := d.jks.GetAllBuildIds(context.Background(), folder+"/job/"+jobName)
	if err != nil {
		return nil, err
	}
	ids := make([]int, 0, len(buildIDs))
	for _, build := range buildIDs {
		ids = append(ids, int(build.Number))
	}
	return ids, nil
}

func (d *devOps) NextPendingInputAction(jobName string, folder string, buildNumber int) (*api_type.InputAction, error) {
	endpoint := fmt.Sprintf("/job/%s/job/%s/%d/wfapi/nextPendingInputAction", folder, jobName, buildNumber)
	inputAction := api_type.InputAction{}
	_, err := d.jks.Requester.Get(context.Background(), endpoint, &inputAction, nil)
	if err != nil {
		return nil, err
	}

	return &inputAction, nil
}

func (d *devOps) JenkinsInputSubmit(jobName string, folder string, buildNumber int, Stage string, Name string, Value string) error {
	var body string
	if Name == "" {
		body = `{"parameter": []}`
	} else {
		body = fmt.Sprintf(`{"parameter": [{"name": "%s", "value": "%s"}]}`, Name, Value)
	}
	endpoint := fmt.Sprintf("%s/job/%s/job/%s/%d/wfapi/inputSubmit?inputId=%s", d.jks.Server, folder, jobName, buildNumber, Stage)
	client := &http.Client{}
	req, err := http.NewRequest("POST", endpoint, strings.NewReader("json="+body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	crumbData := map[string]string{}
	response, _ := d.jks.Requester.GetJSON(context.Background(), "/crumbIssuer/api/json", &crumbData, nil)
	if response.StatusCode == 200 && crumbData["crumbRequestField"] != "" {
		req.SetBasicAuth(d.jks.Requester.BasicAuth.Username, d.jks.Requester.BasicAuth.Password)
		req.Header.Set(crumbData["crumbRequestField"], crumbData["crumb"])
		req.Header.Set("Cookie", response.Header.Get("set-cookie"))

	}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}

	return nil
}

func (d *devOps) JenkinsInputAbort(jobName string, folder string, buildNumber int, Stage string) error {
	endpoint := fmt.Sprintf("/job/%s/job/%s/%d/input/%s/abort", folder, jobName, buildNumber, Stage)
	resp, err := d.jks.Requester.Post(context.Background(), endpoint, nil, nil, nil)
	if err != nil || resp.StatusCode != 200 {
		return errors.New("确认失败")
	}

	return nil
}
